import numpy as np
import random
import copy
import datetime
from gomoku.Evaluate import Evaluation


class MaxMin(object):

    def __init__(self):
        self.MIN = -float('inf')  # 负无穷
        self.MAX = float('inf')  # 正无穷
        self.players = [1, 2]
        self.evaluation = Evaluation()

    def maxmin_fun(self, board, player, deep):
        best = self.MIN
        point_available = board.consider_avail()  # 获取可搜索点，只考虑棋子周围的点
        best_point = []
        alpha = -float('inf')
        beta = float('inf')
        self.point_count = 0  # 搜索点计数
        self.cut_count = 0  # 剪枝点计数

        # 减少搜索点：先对每一个可行位置进行评价，按照返回的价值排序，对排前4为的点再进行深度搜索
        test_value = []
        test_p = []
        for j in range(len(point_available)):
            consi_point = point_available[j]
            board.states[consi_point] = player
            v = self.evaluation.evaluate(board, player)
            # 有可连五位置，直接返回
            if v >= 500000:
                return consi_point
            test_value.append(v)
            board.states.pop(consi_point)
        test_result = dict(zip(point_available, test_value))
        test_result = sorted(test_result.items(), key=lambda x: x[1], reverse=True)
        for i in range(len(test_result)):
            test_p.append(test_result[i][0])

        # 深度搜索
        for i in range(6):
            self.point_count += 1
            current_point = test_p[i]
            board.states[current_point] = player
            board.availables.remove(current_point)
            opplayer = (self.players[0] if player == self.players[1]
                        else self.players[1])
            # 更新alpha值
            value = self.min_fun(board, opplayer, best if (best > alpha) else alpha, beta, deep - 1)
            if (value == best):
                best_point.append(current_point)
            elif (value > best):
                best = value
                best_point.clear()
                best_point.append(current_point)
            else:
                pass
            # board恢复初始
            board.states.pop(current_point)
            board.availables.append(current_point)

        # 对筛选的点随机选取
        result = random.choice(best_point)
        return result

    def max_fun(self, board, player, alpha, beta, deep):
        # 评价当前局势
        v = self.evaluation.evaluate(board, player)
        # 判断输赢、结束
        end, _ = board.has_a_winner()
        # 若达到搜索深度或搜索到棋局结束，返回评价值
        if (deep <= 0 or end == True):
            return v

        # max层的best和alpha初设一致
        best = self.MIN
        point_available = board.consider_avail()

        # 减少搜索数，方法同上
        test_value1 = []
        test_p1 = []
        for j in range(len(point_available)):
            consi_point = point_available[j]
            board.states[consi_point] = player
            v = self.evaluation.evaluate(board, player)
            test_value1.append(v)
            board.states.pop(consi_point)
        test_result1 = dict(zip(point_available, test_value1))
        test_result1 = sorted(test_result1.items(), key=lambda x: x[1], reverse=True)
        for i in range(len(test_result1)):
            test_p1.append(test_result1[i][0])

        # 深度搜索
        for i in range(6):
            self.point_count += 1
            p = test_p1[i]
            board.states[p] = player
            board.availables.remove(p)
            oppo_player = (self.players[0] if player == self.players[1]
                           else self.players[1])
            value = self.min_fun(board, oppo_player, best if (best > alpha) else alpha, beta, deep - 1)

            # board恢复初始
            board.states.pop(p)
            board.availables.append(p)
            # max层取最大，若value>best=alpha，则修改下界
            if (value > best):
                best = value

            # 当前层的beta是上一层（min层）的最大值，若value比beta大，
            # 即当前层最小是value，比同父的其他子节点最大值还大，因此截掉
            if (value >= beta):
                self.cut_count += 1
                break
        return best

    def min_fun(self, board, player, alpha, beta, deep):
        # 评价当前局势
        v = self.evaluation.evaluate(board, player)
        # 判断输赢、结束
        end, _ = board.has_a_winner()
        # 若达到搜索深度或搜索到棋局结束，返回评价值
        if (deep <= 0 or end == True):
            return v

        best = self.MAX  # min层的best和beta初设一致
        point_available = board.consider_avail()

        # 减少搜索数
        test_value2 = []
        test_p2 = []
        for j in range(len(point_available)):
            consi_point = point_available[j]
            board.states[consi_point] = player
            v = self.evaluation.evaluate(board, player)
            test_value2.append(v)
            board.states.pop(consi_point)
        test_result2 = dict(zip(point_available, test_value2))
        test_result2 = sorted(test_result2.items(), key=lambda x: x[1], reverse=True)
        for i in range(len(test_result2)):
            test_p2.append(test_result2[i][0])

        for i in range(6):
            self.point_count += 1
            p = test_p2[i]
            board.states[p] = player
            board.availables.remove(p)
            oppo_player = (self.players[0] if player == self.players[1]
                           else self.players[1])
            value = self.max_fun(board, oppo_player, alpha, best if (best < beta) else beta, deep - 1)

            # board恢复初始
            board.states.pop(p)
            board.availables.append(p)
            if (value < best):  # min层取最小，若value<best=beta,修改上界
                best = value

            # 当前层的alpha是上一层（max层）最小值，若value比alpha小
            # 即当前层最大取value，比同父其他子节点最小值还小，因此截掉
            if (value <= alpha):
                self.cut_count += 1
                break
        return best


class ABplayer(object):
    def __init__(self):
        self.player = 2
        self.width = 15
        self.height = 15
        self.alphabeta = MaxMin()
        self.deep = 4

    def set_player_ind(self, p):
        # 设置AI玩家id
        self.player = p

    def get_action(self, board):
        # 获取动作
        if len(board.states):
            move = self.alphabeta.maxmin_fun(board, self.player, self.deep)
        else:
            move = 112
        return move

    def __str__(self):
        return "ALPHABETA {}".format(self.player)